﻿// <copyright file="Environment.cs" company="Joe Chung">
// Copyright © 2008 by Joe Chung.
// </copyright>
namespace NScheme
{
    using System;

    public sealed class Environment : NScheme.SchemeUtils
    {
        public object vars;

        public object vals;

        public Environment parent;

        public Environment()
        {
        }

        public Environment(object vars, object vals, Environment parent)
        {
            this.vars = vars;
            this.vals = vals;
            this.parent = parent;

            if (!numberArgsOK(vars, vals))
            {
                warn("wrong number of arguments: expected " + vars + " got " + vals);
            }
        }

        public object define(object var, object val)
        {
            this.vars = cons(var, vars);
            this.vals = cons(val, vals);
            if (val is Procedure && ((Procedure)val).name == "anonymous procedure")
            {
                ((Procedure)val).name = var.ToString();
            }

            return var;
        }

        public Environment defPrim(string name, int id, int minArgs)
        {
            define(name, new Primitive(id, minArgs, minArgs));
            return this;
        }

        public Environment defPrim(string name, int id, int minArgs, int maxArgs)
        {
            define(name, new Primitive(id, minArgs, maxArgs));
            return this;
        }

        public object lookup(string symbol)
        {
            object varlist = this.vars;
            object vallist = this.vals;

            while (varlist != null)
            {
                if (first(varlist) as string == symbol)
                {
                    return first(vallist);
                }
                else if (varlist as string == symbol)
                {
                    return vallist;
                }
                else
                {
                    varlist = rest(varlist);
                    vallist = rest(vallist);
                }
            }

            if (parent != null)
            {
                return parent.lookup(symbol);
            }
            else
            {
                return error("Unbound variable: " + symbol);
            }
        }

        public object set(object var, object val)
        {
            if (!(var is string))
            {
                return error("Attempt to set a non-symbol: " + stringify(var));
            }

            string symbol = (string)var;
            object varList = vars;
            object valList = vals;

            while (varList != null)
            {
                if (first(varList) as string == symbol)
                {
                    return setFirst(valList, val);
                }
                else if (rest(varList) as string == symbol)
                {
                    return setRest(valList, val);
                }
                else
                {
                    varList = rest(varList);
                    valList = rest(valList);
                }
            }

            if (parent != null)
            {
                return parent.set(symbol, val);
            }
            else
            {
                return error("Unbound variable: " + symbol);
            }
        }

        private bool numberArgsOK(object vars, object vals)
        {
            return (vars == null && vals == null)
                || (vars is string)
                || (vars is Pair && vals is Pair && numberArgsOK(((Pair)vars).rest, ((Pair)vals).rest));
        }
    }
}